package com.infinit.selfhelp.common.ext

import com.infinit.selfhelp.common.FIREBASE_DATE_FORMAT_PATTERN
import com.infinit.selfhelp.common.dateFormats
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.Hours
import org.joda.time.Interval
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Formatter
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.math.abs

private val DAY_OF_WEEK_FORMAT = SimpleDateFormat("EEEE", Locale.ENGLISH)
private val MONTH_AND_DAY_FORMAT = SimpleDateFormat("MMM d", Locale.ENGLISH)
private val TIME_FORMAT = SimpleDateFormat("hh:mma", Locale.ENGLISH)
private val WEEK_DAY_MONTH_YEAR_FORMAT = SimpleDateFormat("EEEE d MMM yyyy", Locale.ENGLISH)
private val DAY_MONTH_HOUR_MINS = SimpleDateFormat("d MMM, hh:mm a", Locale.ENGLISH)
private val DAY_MONTH_FORMAT = SimpleDateFormat("d MMM", Locale.ENGLISH)
private val DAY_MONTH_NAME_YEAR = SimpleDateFormat("dd MMMM yyyy", Locale.ENGLISH)
private val DAY_ORDINAL_MONTH_NAME_YEAR = SimpleDateFormat("d'%s' MMMM yyyy", Locale.ENGLISH)
private val HOUR_MINS_DAY_ORDINAL_MONTH_NAME_YEAR = SimpleDateFormat(
    "hh:mma, d'%s' MMMM yyyy",
    Locale.ENGLISH
)

private val DAY_MONTH_NUMBER_YEAR = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
private val FUTURE_MATCH = SimpleDateFormat("hh:mma EEE dd MMM", Locale.ENGLISH)
private val WEEK_DAY_DAY = SimpleDateFormat("EEE dd", Locale.ENGLISH)
private val WEEK_DAY_DATE_MONTH = SimpleDateFormat("EEE d MMM", Locale.ENGLISH)
private val DAY_MONTH_YEAR = SimpleDateFormat("d MMM yy", Locale.ENGLISH)
val iosStr = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
private val ISO = SimpleDateFormat(iosStr, Locale.ENGLISH)
val YEAR_MONTH_DAY = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
val weekDayMonthYearStr = "E, dd MMM yyyy"
val timeStr = "hh:mm a"
private val LEAP_YEAR = "1/29"

private val NOTIFICATION_FORMAT = SimpleDateFormat("E, dd MMM yyyy HH:mm", Locale.ENGLISH)

@Synchronized
fun DateTime.formatDayOfWeek(): String = DAY_OF_WEEK_FORMAT.format(this.toDate())

@Synchronized
fun DateTime.formatTime(): String = TIME_FORMAT.format(this.toDate())

@Synchronized
fun DateTime.formatMonthAndDay(): String = MONTH_AND_DAY_FORMAT.format(this.toDate())

@Synchronized
fun DateTime.formatWeekDayMonthYear(): String = WEEK_DAY_MONTH_YEAR_FORMAT.format(this.toDate())

@Synchronized
fun DateTime.formatDayMonthHourMins(): String = DAY_MONTH_HOUR_MINS.format(this.toDate()).replace(
    "AM",
    "am"
).replace("PM", "pm")

@Synchronized
fun DateTime.formatDayAndMonth(): String = DAY_MONTH_FORMAT.format(this.toDate())

@Synchronized
fun DateTime.formatDayMonthNameYear(): String = DAY_MONTH_NAME_YEAR.format(this.toDate())

@Synchronized
fun DateTime.formatOrdinalDayMonthNameYear(): String = DAY_ORDINAL_MONTH_NAME_YEAR.format(
    this.toDate()
).let {
    String.format(it, dayOfMonthSuffix())
}

@Synchronized
fun DateTime.formatHourMinsOrdinalDayMonthNameYear(): String = HOUR_MINS_DAY_ORDINAL_MONTH_NAME_YEAR.format(
    this.toDate()
).let {
    String.format(it, dayOfMonthSuffix())
}.replace("AM", "am").replace("PM", "pm")

@Synchronized
private fun DateTime.dayOfMonthSuffix(): String {
    val dayInt = dayOfMonth().get()
    return when {
        (dayInt % 10 == 1) && (dayInt != 11) -> "st"
        (dayInt % 10 == 2) && (dayInt != 12) -> "nd"
        (dayInt % 10 == 3) && (dayInt != 13) -> "rd"
        else -> "th"
    }
}

@Synchronized
fun DateTime.formatDayMonthNumberYear(): String = DAY_MONTH_NUMBER_YEAR.format(this.toDate())

@Synchronized
fun getAfter30DayMonthNumberYear(): String = DAY_MONTH_NUMBER_YEAR.format(
    DateTime.now().plusDays(30).toDate()
)

@Synchronized
fun getTodayMonthNumberYear(): String = DAY_MONTH_NUMBER_YEAR.format(
    DateTime.now().plusDays(1).toDate()
)

@Synchronized
fun DateTime.formatWeekDayDay(): String = WEEK_DAY_DAY.format(this.toDate())

@Synchronized
fun DateTime.formatWeekDayDateMonth(): String = WEEK_DAY_DATE_MONTH.format(this.toDate())

@Synchronized
fun DateTime.formatDayMonthYear(): String = DAY_MONTH_YEAR.format(this.toDate())

@Synchronized
fun DateTime.formatIso(): String = ISO.format(this.toDate())

@Synchronized
fun DateTime.formatYearMonthDay(): String = YEAR_MONTH_DAY.format(this.toDate())

@Synchronized
fun Date.formatDayMonthNumberYear(): String = DAY_MONTH_NUMBER_YEAR.format(this)

@Synchronized
fun Date.formatDayMonthNameYear(): String = DAY_MONTH_NAME_YEAR.format(this)

@Synchronized
fun Date.formatFutureMatchTime(): String = FUTURE_MATCH.format(this)

@Synchronized
fun DateTime.formatNotificationTime(): String = NOTIFICATION_FORMAT.format(this.toDate())

fun DateTime.isCurrentWeek(): Boolean {
    val now = DateTime.now()
    return weekOfWeekyear == now.weekOfWeekyear && weekyear == now.weekyear && era == now.era
}

fun DateTime.isCurrentMonth(): Boolean {
    val now = DateTime.now()
    return year == now.year && monthOfYear == now.monthOfYear && era == now.era
}

fun Int.isSameMonth(): Boolean {
    val now = DateTime.now()
    return this == now.monthOfYear
}

fun DateTime.isCurrentYear(): Boolean {
    return year == DateTime.now().year
}

fun DateTime.isToady(): Boolean {
    val now = DateTime.now()
    return year == now.year && dayOfYear == now.dayOfYear && era == now.era
}

fun DateTime.isOver200Year(): Boolean {
    val now = DateTime.now()
    return if (this.isAfter(now)) {
        true
    } else {
        this.plusYears(200).plusDays(1).isBeforeNow
    }
}

fun DateTime.formatTimeDistance(): String {
    val distance = abs(this.millis - DateTime.now(DateTimeZone.getDefault()).millis)
    val formatBuilder = StringBuilder()
    val formatter = Formatter(formatBuilder, Locale.getDefault())
    return getStringForTime(formatBuilder, formatter, distance)
}

fun DateTime.formatPastMatchTime(): String {
    return if (isCurrentYear()) formatDayAndMonth() else formatDayMonthYear()
}

fun Int.formatTimeDistance(): String {
    val formatBuilder = StringBuilder()
    val formatter = Formatter(formatBuilder, Locale.getDefault())
    return getStringForTime(formatBuilder, formatter, this.toLong() * 1000)
}

fun DateTime.getMinuteDistance(): Int {
    val distance = abs(this.millis - DateTime.now(DateTimeZone.getDefault()).millis)
    return (distance / TimeUnit.MINUTES.toMillis(1)).toInt()
}

fun DateTime.getHourDistance(): Int {
    val distance = abs(this.millis - DateTime.now(DateTimeZone.getDefault()).millis)
    return (distance / TimeUnit.HOURS.toMillis(1)).toInt()
}

fun DateTime.getDayDistance(): Int {
    val distance = abs(this.millis - DateTime.now(DateTimeZone.getDefault()).millis)
    return (distance / TimeUnit.DAYS.toMillis(1)).toInt()
}

fun DateTime.dayStartTime(): DateTime {
    return withMillisOfDay(0) // return yyyy-MM-dd 00:00:00
}

fun DateTime.dayEndTime(): DateTime {
    return withMillisOfDay(0).plusDays(1).minusSeconds(1) // return yyyy-MM-dd 23:59:59
}

fun String.toDayMonthYear(): DateTime = DateTime(DAY_MONTH_NAME_YEAR.parse(this))
fun String.toDayMonthNumberYear(): DateTime = DateTime(DAY_MONTH_NUMBER_YEAR.parse(this))

fun getStringForTime(builder: StringBuilder, formatter: Formatter, timeMs: Long): String {
    var timeMillis = timeMs
    if (timeMillis == -9223372036854775807L) {
        timeMillis = 0L
    }
    val totalSeconds: Long = (timeMillis + 500L) / 1000L
    val seconds = totalSeconds % 60L
    val minutes = totalSeconds / 60L % 60L
    val hours = totalSeconds / 3600L
    builder.setLength(0)
    return if (hours > 0L) {
        formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
    } else {
        formatter.format(
            "%02d:%02d",
            minutes,
            seconds
        ).toString()
    }
}

fun getHourDuration(dateFrom: DateTime, dateTo: DateTime): String {
    val hour = abs(Hours.hoursBetween(dateFrom, dateTo).hours)
    return if (hour > 1) "$hour hours" else "$hour hour"
}

// #reference link https://andreabergia.com/blog/2015/11/parsing-dates-in-multiple-formats-in-joda-time/

fun String.toDateTime(): DateTime {
    return this.toDateTimeCatchError()
}

fun String.toDateTimeCatchError(): DateTime {
    var parseDateTime: DateTime = try {
        dateFormats.parseDateTime(this)
    } catch (e: Exception) {
        try {
            dateFormats.withZone(DateTimeZone.UTC).parseDateTime(this)
        } catch (e: Exception) {
            DateTime()
        }
    }
    return parseDateTime
}

fun String.getTimeInMillis(): Long {
    val formatter = SimpleDateFormat(FIREBASE_DATE_FORMAT_PATTERN, Locale.ENGLISH)
    val dt = formatter.parse(this)
    val calendar = Calendar.getInstance()
    calendar.time = dt
    return calendar.timeInMillis
}

fun String.formatYMD(): String {
    if (this.contains("9999")) {
        return ""
    }
    val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
    val tempDate = formatter.parse(this)
    val formatter2 = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
    return formatter2.format(tempDate!!)
}

fun String.isBeforeNow(format: String): Boolean {
    return try {
        val inputDateFormat = SimpleDateFormat(format, Locale.US)
        val inputCalendar = Calendar.getInstance()
        inputCalendar.time = inputDateFormat.parse(this) as Date
        val currentCalendar = Calendar.getInstance()
        inputCalendar.before(currentCalendar)
    } catch (e: Exception) {
        true
    }
}

fun String.formatDMY(): String {
    if (this.contains("9999")) {
        return ""
    }
    val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH)
    val tempDate = formatter.parse(this)
    val formatter2 = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
    return formatter2.format(tempDate!!)
}

fun Long.formatDMY(): String {
    val formatter2 = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatYMDHMS(): String {
    val formatter2 = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatY_M_D(): String {
    val formatter2 = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatD_M_Y_H_M_A(): String {
    val formatter2 = SimpleDateFormat("EEE, dd MMM yyyy • hh:mm a", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date).replace(" am", " AM").replace(" pm", " PM")
}

fun Long.formatD_M_Y_H_M_A_GA(): String {
    val formatter2 = SimpleDateFormat("dd/MMM/yyyy-hh:mm a", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date).replace(" am", " AM").replace(" pm", " PM")
}

fun String.formatDMYHMAToLong(): Long {
    val formatter = SimpleDateFormat("EEE, dd MMM yyyy • hh:mm a", Locale.ENGLISH)
    val tempDate = formatter.parse(this)
    if (tempDate != null) {
        return tempDate.time
    }
    return 0L
}

fun Long.formatD_M_Y(): String {
    if (this == 0L) {
        return ""
    }
    val formatter2 = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatD_M_Y_GA(): String {
    if (this == 0L) {
        return ""
    }
    val formatter2 = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formath_d_Singel(): String {
    if (this == 0L) {
        return ""
    }
    val formatter2 = SimpleDateFormat("h/m/a", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatEDMY(): String {
    val formatter2 = SimpleDateFormat("E, dd MMM yyyy", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date)
}

fun Long.formatHMA(): String {
    val formatter2 = SimpleDateFormat("hh:mm a", Locale.ENGLISH)
    val date = Date(this)
    return formatter2.format(date).uppercase()
}

fun Long.convertDataTimeStr(format: SimpleDateFormat): String {
    val date = Date(this)
    return format.format(date)
}

fun Int.exceedRequiredDays(startDate: Long, endDate: Long): Boolean {
    val betweenDays = (endDate - startDate) / (1000L * 3600L * 24L)
    return this < betweenDays.toInt()
}

fun Int.exceedRequiredHours(startDate: Long, endDate: Long): Boolean {
    val betweenHours = (endDate - startDate).toFloat() / (1000L * 3600L)
    return this < betweenHours
}

fun Int.exceedRequiredMinutes(startDate: Long, endDate: Long): Boolean {
    val betweenHours = (endDate - startDate).toFloat() / (1000L * 60L)
    return this < betweenHours
}

fun String.formatDMYYMD(): String {
    if (this.contains("9999")) {
        return ""
    }
    return try {
        val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
        val tempDate = formatter.parse(this)
        val formatter2 = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
        formatter2.format(tempDate)
    } catch (e: Exception) {
        ""
    }
}

fun formatTripDate(dateTime: DateTime, pattern: String): String {
    val simpleDateFormat = SimpleDateFormat(pattern, Locale.ENGLISH)
    return simpleDateFormat.format(dateTime.toDate())
}

fun durationCurrentTime(dateTime: String?, pattern: String): Long {
    return try {
        val simpleDateFormat = SimpleDateFormat(pattern, Locale.ENGLISH)
        var sTime: Date = simpleDateFormat.parse(dateTime)
        var eTime: Date = Calendar.getInstance().time
        val diff = abs(eTime.time - sTime.time)
        diff / 1000
    } catch (e: Exception) {
        0
    }
}

fun Long.getNextYearTimestamp(): Long {
    val calendar = Calendar.getInstance()
    calendar.timeInMillis = this
    val date = "${calendar.get(Calendar.MONTH)}/${calendar.get(Calendar.DAY_OF_MONTH)}"
    calendar.add(Calendar.YEAR, 1)
    if (date == LEAP_YEAR) {
        calendar.set(Calendar.DATE, calendar.get(Calendar.DATE))
    } else {
        calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) - 1)
    }

    return calendar.timeInMillis
}

fun isCurrentTimeInInterval(startDateTimeStr: String?, endDateTimeStr: String?): Boolean {
    val currentDateTime = DateTime.now()
    val startDateTime = startDateTimeStr?.toDateTime()
    val endDateTime = endDateTimeStr?.toDateTime()
    return if (startDateTime?.isBefore(endDateTime) == true) {
        try {
            val interval = Interval(startDateTime, endDateTime)
            interval.contains(currentDateTime)
        } catch (e: Exception) {
            false
        }
    } else {
        false
    }
}

fun Long.longGetCurrentDateStart(): Long {
    val calendar = Calendar.getInstance()
    calendar.timeInMillis = this
    calendar.set(Calendar.HOUR_OF_DAY, 0)
    calendar.set(Calendar.SECOND, 0)
    calendar.set(Calendar.MINUTE, 0)
    calendar.set(Calendar.MILLISECOND, 0)
    return calendar.timeInMillis
}

fun Long.longGetCurrentDateEnd(): Long {
    val longGetCurrentDateStart = this.longGetCurrentDateStart()
    val calendar = Calendar.getInstance()
    calendar.timeInMillis = longGetCurrentDateStart
    calendar.set(Calendar.HOUR_OF_DAY, 23)
    calendar.set(Calendar.SECOND, 59)
    calendar.set(Calendar.MINUTE, 59)
    calendar.set(Calendar.MILLISECOND, 999)
    return calendar.timeInMillis
}
